#include "tab_comm.h"
#include "client_print.h"

#define ESC_SEQ 0xFF

const uint CTabComm::sStreamDataShowCount(5);

CTabComm::CTabComm(QWidget* pParent) :
	QWidget(pParent),
	mProtocolStat(STAT_NONE),
	mChatLength(0),
	mChatIndex(0),
	mChatMessage(""),
	mChatAppendSync(QMutex::Recursive),
	mSections(this),
	mChatRecvInfoLabel("No receiving"),
	mChatWindow(new QPlainTextEdit()),
	mChatEditLine(),
	mChatEdit(),
	mChatButton("&Send"),
	mSeperator(),
	mStreamLine(),
	mStreamSendBox("Stream send"),
	mStreamSendLayout(),
	mStreamSendLabel(""),
	mStreamSendDataGrid(),
	mStreamSendData(NULL),
	mStreamRecvBox("Stream recv"),
	mStreamRecvLayout(),
	mStreamRecvLabel(""),
	mStreamRecvDataGrid(),
	mStreamRecvData(NULL),
	mPacketLine(),
	mPacketSendBox("Packets send"),
	mPacketSendLayout(),
	mPacketSendLabel(""),
	mPacketRecvBox("Packets recv"),
	mPacketRecvLayout(),
	mPacketRecvLabel(""),
	mFrameLine(),
	mFrameSendBox("Frame send"),
	mFrameSendLayout(),
	mFrameSendLabel(""),
	mFrameRecvBox("Frame recv"),
	mFrameRecvLayout(),
	mFrameRecvLabel(""),
	mAmpLine(),
	mAmpEmbed(),
	mAmpExtract(),
	mBufferLine(),
	mBufferLabel("Extract buffer:"),
	mBufferProgressBar()
{
	mStreamSendData = new QLabel**[3];
	mStreamRecvData = new QLabel**[3];
	for (uint i = 0; i < 3; i++) {
		mStreamSendData[i] = new QLabel*[sStreamDataShowCount];
		mStreamRecvData[i] = new QLabel*[sStreamDataShowCount];
	}

	resetCounters();
	setupUiElements();
	resetInfoLabels();
	setupActions();
}

CTabComm::~CTabComm()
{
	for (uint i = 0; i < 3; i++) {
		delete[] mStreamRecvData[i];
		delete[] mStreamSendData[i];
	}
	delete[] mStreamRecvData;
	delete[] mStreamSendData;
}

void CTabComm::resetCounters()
{
	mStreamPayloadSendCount = 0;
	mStreamSendByteCount = 0;
	mStreamPayloadRecvCount = 0;
	mStreamRecvByteCount = 0;
	mPacketSendCountGood = -1;
	mPacketSendCount = -1;
	mPayloadSendCount = 0;
	mPacketRecvCountGood = -1;
	mPacketRecvCount = -1;
	mPayloadRecvCount = 0;
	mFrameSendCount = -1;
	mFrameRecvCount = -1;
}

void CTabComm::resetInfoLabels()
{
	mFrameSendLabel.setText(updateFrameInfo(mFrameSendCount, 0));
	mFrameRecvLabel.setText(updateFrameInfo(mFrameRecvCount, 0));

	mPacketSendLabel.setText(updatePacketInfo(mPacketSendCountGood, mPacketSendCount, mPayloadSendCount, 0));
	mPacketRecvLabel.setText(updatePacketInfo(mPacketRecvCountGood, mPacketRecvCount, mPayloadRecvCount, 0));

	mStreamSendLabel.setText(updateStreamInfo(mStreamPayloadSendCount, -1, -1, 0));
	mStreamRecvLabel.setText(updateStreamInfo(mStreamPayloadRecvCount, -1, -1, 0));

	for (uint i = 0; i < sStreamDataShowCount; i++) {
		for (uint j = 0; j < 3; j++) {
			mStreamSendData[j][i]->setText("-");
			mStreamRecvData[j][i]->setText("-");
		}
	}
}

void CTabComm::setupUiElements()
{
	// chat
	mChatWindow->setReadOnly(true);
	mChatEditLine.addWidget(&mChatEdit);
	mChatEditLine.addWidget(&mChatButton);

	// seperator
	mSeperator.setFrameShape(QFrame::HLine);
	mSeperator.setFrameShadow(QFrame::Sunken);

	// stream
	for (uint i = 0; i < sStreamDataShowCount; i++) {
		for (uint j = 0; j < 3; j++) {
			mStreamSendData[j][i] = new QLabel("-");
			mStreamSendData[j][i]->setAlignment(Qt::AlignHCenter);
			mStreamSendData[j][i]->setFrameStyle(QFrame::Box);
			mStreamSendDataGrid.addWidget(mStreamSendData[j][i], j, i);

			mStreamRecvData[j][i] = new QLabel("-");
			mStreamRecvData[j][i]->setAlignment(Qt::AlignHCenter);
			mStreamRecvData[j][i]->setFrameStyle(QFrame::Box);
			mStreamRecvDataGrid.addWidget(mStreamRecvData[j][i], j, i);
		}
	}
	mStreamSendLayout.addWidget(&mStreamSendLabel);
	mStreamSendLayout.addLayout(&mStreamSendDataGrid);
	mStreamRecvLayout.addWidget(&mStreamRecvLabel);
	mStreamRecvLayout.addLayout(&mStreamRecvDataGrid);
	mStreamSendBox.setLayout(&mStreamSendLayout);
	mStreamRecvBox.setLayout(&mStreamRecvLayout);
	mStreamLine.addWidget(&mStreamSendBox);
	mStreamLine.addWidget(&mStreamRecvBox);

	// packet
	mPacketSendLayout.addWidget(&mPacketSendLabel);
	mPacketRecvLayout.addWidget(&mPacketRecvLabel);
	mPacketSendBox.setLayout(&mPacketSendLayout);
	mPacketRecvBox.setLayout(&mPacketRecvLayout);
	mPacketLine.addWidget(&mPacketSendBox);
	mPacketLine.addWidget(&mPacketRecvBox);

	// frame
	mFrameSendLayout.addWidget(&mFrameSendLabel);
	mFrameRecvLayout.addWidget(&mFrameRecvLabel);
	mFrameSendBox.setLayout(&mFrameSendLayout);
	mFrameRecvBox.setLayout(&mFrameRecvLayout);
	mFrameLine.addWidget(&mFrameSendBox);
	mFrameLine.addWidget(&mFrameRecvBox);

	// amplitude
	mAmpEmbed.setTextVisible(false);
	mAmpEmbed.setMaximum(255);
	mAmpExtract.setTextVisible(false);
	mAmpExtract.setMaximum(255);
	mAmpLine.addWidget(&mAmpEmbed);
	mAmpLine.addWidget(&mAmpExtract);

	// extract buffer
	mBufferProgressBar.setFormat("%p% (%v/%m byte)");
	mBufferLine.addWidget(&mBufferLabel);
	mBufferLine.addWidget(&mBufferProgressBar);

	// build view
	mSections.addWidget(&mChatRecvInfoLabel);
	mSections.addWidget(mChatWindow);
	mSections.addLayout(&mChatEditLine);
	mSections.addWidget(&mSeperator);
	mSections.addLayout(&mStreamLine);
	mSections.addLayout(&mPacketLine);
	mSections.addLayout(&mFrameLine);
	mSections.addLayout(&mAmpLine);
	mSections.addLayout(&mBufferLine);

	setLayout(&mSections);
}

void CTabComm::setupActions()
{
	connect(&mChatEdit, SIGNAL(returnPressed()), this, SLOT(chatButtonClicked()));
	connect(&mChatButton, SIGNAL(clicked()), this, SLOT(chatButtonClicked()));
}

void CTabComm::chatButtonClicked()
{
	if (QString::compare(mChatEdit.text(), "") != 0) {
		QString lMessage(mChatEdit.text());
		//mChatWindow->appendPlainText(lMessage);
		//mChatEdit.setText("");
		if (lMessage.length() > 254) {
			lMessage.truncate(254);
		}
		std::string lMsg(lMessage.toStdString());
		unsigned char* lData(new unsigned char [lMsg.length() + 2]);
		lData[0] = 0xFF;
		lData[1] = lMsg.length();
		for (unsigned int i = 0; i < lMsg.length(); i++) {
			lData[i + 2] = lMsg[i];
		}
		// TODO check if there is a connection
		emit protocolReadDataSend(lData, lMsg.length() + 2);
		delete [] lData;
		mChatEdit.setEnabled(false);
		mChatButton.setEnabled(false);
		mChatButton.setText("Sending (0/" + QString::number(lMsg.length() + 2) + ")");
	}
}

void CTabComm::extractBufferInfoReceived(quint32 pUsed, quint32 pSize)
{
	mBufferProgressBar.setMaximum(pSize);
	mBufferProgressBar.setValue(pUsed);
}

void CTabComm::frameSendLengthReceived(quint32 pLength)
{
	mFrameSendLabel.setText(updateFrameInfo(mFrameSendCount, pLength));
}

void CTabComm::frameRecvLengthReceived(quint32 pLength, bool pError)
{
	mFrameRecvLabel.setText(updateFrameInfo(mFrameRecvCount, pLength, pError));
}

void CTabComm::packetSendLengthReceived(quint32 pLength)
{
	mPacketSendLabel.setText(updatePacketInfo(mPacketSendCountGood, mPacketSendCount, mPayloadSendCount, pLength));
}

void CTabComm::packetRecvLengthReceived(quint32 pLength, bool pCrcError)
{
	mPacketRecvLabel.setText(updatePacketInfo(mPacketRecvCountGood, mPacketRecvCount, mPayloadRecvCount, pLength, pCrcError ? -1 : 1));
}

void CTabComm::streamSendInfoReceived(int pId, int pAck, quint32 pLength, bool pReplay)
{
	mStreamSendLabel.setText(updateStreamInfo(mStreamPayloadSendCount, pId, pAck, pLength, pReplay));
}

void CTabComm::streamSendDataReceived(void* pData, int pLength)
{
	updateStreamData(mStreamSendData, mStreamSendByteCount, pData, pLength);
}

void CTabComm::streamRecvInfoReceived(int pId, int pAck, quint32 pLength, bool pReplay)
{
	mStreamRecvLabel.setText(updateStreamInfo(mStreamPayloadRecvCount, pId, pAck, pLength, pReplay));
}

void CTabComm::streamRecvDataReceived(void* pData, int pLength)
{
	updateStreamData(mStreamRecvData, mStreamRecvByteCount, pData, pLength);
}

void CTabComm::protocolWriteDataReceived(void* pData, int pLength)
{
	if (!pData || pLength <= 0) {
		return;
	}

	const unsigned char* lData(static_cast<const unsigned char*>(pData));
	for (int i = 0; i < pLength; i++) {
		switch (mProtocolStat) {
			case STAT_NONE:
				if (lData[i] != ESC_SEQ) {
					CClientPrint::print("Chat/File Error (no 0xFF)");
					mChatRecvInfoLabel.setText("Chat/File error.");
					break;
				}
				mProtocolStat = STAT_NEW_CHAT_OR_FILE;
				mChatRecvInfoLabel.setText("Receiving ...");
				break;
			case STAT_NEW_CHAT_OR_FILE:
				if (lData[i] >= 1 && lData[i] <= 254) {
					mChatLength = lData[i];
					mChatIndex = 0;
					mChatMessage = "";
					mProtocolStat = STAT_CHAT;
					mChatRecvInfoLabel.setText("Receiving new Message (" +
							QString::number(mChatIndex) + "/" +
							QString::number(mChatLength) + ")");
				}
				else {
					CClientPrint::print("Chat/File Error (no file or long msg chat support)");
					mChatRecvInfoLabel.setText("Chat/File error.");
					mProtocolStat = STAT_NONE; // reset to beginning
				}
				break;
			case STAT_CHAT:
				if (lData[i] == ESC_SEQ) {
					CClientPrint::print("Chat/File Error (0xFF in data)");
					mChatRecvInfoLabel.setText("Chat/File error.");
					mProtocolStat = STAT_NONE; // reset to beginning
					break;
				}

				{
					char szLetter[2] = { '?', '\0' };
					szLetter[0] = static_cast<char>(lData[i]);
					mChatMessage += szLetter;
					mChatIndex++;

					if (mChatIndex >= mChatLength) {
						mChatAppendSync.lock();
						mChatWindow->appendPlainText("other: " + mChatMessage);
						mChatAppendSync.unlock();

						mProtocolStat = STAT_NONE;
						mChatMessage = "";
						mChatLength = 0;
						mChatIndex = 0;
						mChatRecvInfoLabel.setText("No receiving");
					}
					else {
						mChatRecvInfoLabel.setText("Receiving new Message (" +
								QString::number(mChatIndex) + "/" +
								QString::number(mChatLength) + ")");
					}
				}

				break;
		}
	}
}

void CTabComm::protocolReadFeedbackReceived(quint32 pRead, quint32 pSize)
{
	if (pRead < pSize) {
		mChatButton.setText("Sending (" + QString::number(pRead) + "/" + QString::number(pSize) + ")");
	}
	else {
		mChatButton.setText("&Send");
		mChatAppendSync.lock();
		mChatWindow->appendPlainText("me: " + mChatEdit.text());
		mChatAppendSync.unlock();
		mChatEdit.setText("");
		mChatEdit.setEnabled(true);
		mChatButton.setEnabled(true);
	}
}

void CTabComm::embedAmpReceived(quint8 pAmp)
{
	mAmpEmbed.setValue(pAmp);
}

void CTabComm::extractAmpReceived(quint8 pAmp)
{
	mAmpExtract.setValue(pAmp);
}

QString CTabComm::updateStreamInfo(ulong& pPayloadCount, int pId, int pAck, quint32 pPayloadLength, bool pReplay)
{
	if (!pReplay) {
		pPayloadCount += pPayloadLength;
	}

	QString lId(QString::number(pId));
	if (pId == -1) {
		lId = "-";
	}

	QString lAck(QString::number(pAck));
	if (pAck == -1) {
		lAck = "-";
	}

	return "Id: " + lId + ", Ack: " + lAck + (pReplay ? " (duplicate)": "") +
		"\n" + "Total stego payload: " + QString::number(pPayloadCount) + " bytes";
}

void CTabComm::updateStreamData(QLabel***& pStreamData, uint& pStreamByteCount, void* pData, int pLength)
{
	unsigned char* lData(reinterpret_cast<unsigned char*>(pData));

	int lSkip(pLength - sStreamDataShowCount);
	if (lSkip > 0) {
		lData += lSkip;
		pLength -= lSkip;
	}

	int lFree(sStreamDataShowCount - pStreamByteCount);
	if (lFree < 0 || lFree < pLength) {
		uint lShift(pLength - ((lFree > 0) ? lFree : 0));
		do {
			for (uint i = 0; i < sStreamDataShowCount - 1; i++) {
				for (uint j = 0; j < 3; j++) {
					pStreamData[j][i]->setText(pStreamData[j][i + 1]->text());
				}
			}
		} while (--lShift);
	}

	int lStart(sStreamDataShowCount - pLength);
	if (lFree > 0) {
		lStart = sStreamDataShowCount - lFree;
	}
	for (int i = 0, j = lStart; i < pLength; i++, j++) {
		pStreamData[0][j]->setText(QString::number(pStreamByteCount++));
		pStreamData[1][j]->setText("0x" + QString::number(lData[i], 16));
		pStreamData[2][j]->setText("??");

		if (lData[i] >= 0x20 && lData[i] <= 0x7E) { // printable char
			pStreamData[2][j]->setText(QString::fromAscii(&reinterpret_cast<char*>(lData)[i], 1));
		}
	}
}

QString CTabComm::updatePacketInfo(int& pPacketCountGood, int& pPacketCount, ulong& pPayloadCount, quint32 pPayloadLength, int pCrcError)
{
	if (pCrcError != -1) {
		pPacketCountGood++;
		pPayloadCount += pPayloadLength;
	}
	pPacketCount++;

	QString lCrcMessage("");
	if (pCrcError == -1) {
		lCrcMessage = ", CRC: wrong !!!";
	} else if (pCrcError == 1) {
		lCrcMessage = ", CRC: right";
	}

	return "Count: " + QString::number(pPacketCountGood) + "/" +
		QString::number(pPacketCount) + ", Length: " +
		QString::number(pPayloadLength) + " bytes" + lCrcMessage + "\n" +
		"Total payload: " + QString::number(pPayloadCount) + " bytes";
}

QString CTabComm::updateFrameInfo(int& pFrameCount, quint32 pFrameLength, bool pError)
{
	if (!pError) {
		pFrameCount++;
	}

	return "Count: " + QString::number(pFrameCount) + ", Length: " +
		QString::number(pFrameLength) + " bits" + (pError ? " !!!" : "");
}

